package com.szx.java.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.szx.java.listener.UploadProgressListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author songzx
 * @create 2023-06-08 14:50
 */
@Service
public class FileServiceImpl {
  @Value("${upload.directory}")
  private String uploadDirectory;

  @Value("${oss.endpoint}")
  private String endpoint;

  @Value("${oss.accessKeyId}")
  private String accessKeyId;

  @Value("${oss.accessKeySecret}")
  private String accessKeySecret;

  @Value("${oss.bucketName}")
  private String bucketName;

  @Value("${oss.prefix}")
  private String prefix;

  /**
   * 文件上传
   *
   * @param file
   * @param chunkNumber
   * @param totalChunks
   * @return
   */
  public Boolean uploadFileByCondition(MultipartFile file, int chunkNumber, int totalChunks) {
    // 创建目录
    File directory = new File(uploadDirectory);
    if (!directory.exists()) {
      directory.mkdirs();
    }
    String fileName = file.getOriginalFilename();
    try {
      // 将分片保存到磁盘
      String filePath = uploadDirectory + fileName + "_part_" + chunkNumber;
      Path path = Paths.get(filePath);
      Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
      // 判断是否已经接收到所有分片
      if (chunkNumber == totalChunks) {
        // 所有分片已接收，开始合并文件
        mergeFile(uploadDirectory, fileName, totalChunks);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
    return true;
  }

  /**
   * 合并文件
   *
   * @param filename
   * @param totalChunks
   */
  private void mergeFile(String samplePath, String filename, int totalChunks) {
    String mergedFilePath = samplePath + filename;
    Path mergedPath = Paths.get(mergedFilePath);
    // 逐个合并分片
    for (int i = 1; i <= totalChunks; i++) {
      String chunkFilePath = samplePath + filename + "_part_" + i;
      Path chunkPath = Paths.get(chunkFilePath);
      try {
        Files.write(
            mergedPath,
            Files.readAllBytes(chunkPath),
            StandardOpenOption.CREATE,
            StandardOpenOption.APPEND);
        Files.delete(chunkPath); // 删除已合并的分片
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 根据文件路径下载文件
   *
   * @param path
   * @param response
   */
  public void downloadFileByPath(String path, HttpServletResponse response) {
    try {
      File file = new File(uploadDirectory + path);
      String filename = file.getName();
      FileInputStream fileInputStream = new FileInputStream(file);
      InputStream fis = new BufferedInputStream(fileInputStream);
      byte[] buffer = new byte[fis.available()];
      fis.read(buffer);
      fis.close();
      response.reset();
      response.setCharacterEncoding("UTF-8");
      response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
      response.addHeader("Content-Length", "" + file.length());
      OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
      response.setContentType("application/octet-stream");
      outputStream.write(buffer);
      outputStream.flush();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 创建OssClient
   *
   * @return
   */
  public OSS createOssClient() {
    // 创建OSSClient实例。
    return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
  }

  /**
   * 文件上传
   *
   * @param file
   * @return
   */
  public String ossUpload(MultipartFile file) {
    try {
      OSS ossClient = createOssClient();
      // 创建文件名，例如 filttest/exampleobject.txt";
      String objectName = prefix + file.getOriginalFilename();
      // 创建InitiateMultipartUploadRequest对象。
      InitiateMultipartUploadRequest request =
          new InitiateMultipartUploadRequest(bucketName, objectName);
      // 初始化分片。
      InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
      // 返回uploadId，它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作，例如取消分片上传、查询分片上传等。
      String uploadId = upresult.getUploadId();
      // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
      List<PartETag> partETags = new ArrayList<PartETag>();
      // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
      final long partSize = 2 * 1024 * 1024L; // 2 MB。
      // 根据上传的数据大小计算分片数。
      long fileLength = file.getSize();
      int partCount = (int) (fileLength / partSize);
      if (fileLength % partSize != 0) {
        partCount++;
      }

      // 添加上传进度器
      UploadProgressListener uploadProgressListener = new UploadProgressListener(fileLength);

      // 遍历分片上传。
      for (int i = 0; i < partCount; i++) {
        long startPos = i * partSize;
        long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(objectName);
        uploadPartRequest.setUploadId(uploadId);

        // 设置上传的分片流。并通过InputStream.skip()方法跳过指定数据。
        final InputStream instream = file.getInputStream();
        instream.skip(startPos);
        uploadPartRequest.setInputStream(instream);
        // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
        uploadPartRequest.setPartSize(curPartSize);
        // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
        uploadPartRequest.setPartNumber(i + 1);
        // 添加上传进度监听器
        uploadPartRequest.setProgressListener(uploadProgressListener);
        // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
        // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
        partETags.add(uploadPartResult.getPartETag());
      }
      // 创建CompleteMultipartUploadRequest对象。
      // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
      CompleteMultipartUploadRequest completeMultipartUploadRequest =
          new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
      // 完成分片上传。
      ossClient.completeMultipartUpload(completeMultipartUploadRequest);
      // 关闭连接
      ossClient.shutdown();
      // 拼接文件线上地址返回给前端
      return "http://" + bucketName + "." + endpoint + "/" + objectName;
    } catch (IOException e) {
      e.printStackTrace();
    }
    return "";
  }
}
